package tomoto.net;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.ObjectUtils;

import tomoto.assertion.AssertUtil;
import tomoto.assertion.ParamUtil;

/**
 * http://tools.ietf.org/html/draft-hoehrmann-urlencoded-01
 * 
 * @author tomoto
 */
public class QueryStringUtil {
    /**
     * Parser of HTTP query string.
     * 
     * @author tomoto
     */
    public static class Parser {
        private Map<String, List<String>> paramMap = new HashMap<String, List<String>>();
        private String encoding;

        /**
         * Creates an instance.
         * 
         * @param encoding Character encoding to use when interpreting URL escapes.
         */
        public Parser(String encoding) {
            ParamUtil.check(Charset.isSupported(encoding));
            this.encoding = encoding;
        }

        /**
         * Creates an instance. URL escapes are interpreted in UTF-8.
         */
        public Parser() {
            this("UTF-8");
        }

        /**
         * Parses the given query string and accumulate the parameters to the
         * parameter map inside.
         * 
         * @param s query string to parse
         */
        public void parse(String s) {
            paramMap.clear();
            
            if (s.isEmpty()) {
                return; // empty set
            }
            
            for (String p : s.split("&", -1)) {
                String[] keyValue = p.split("=", 2);
                try {
                    String key = URLDecoder.decode(keyValue[0], encoding);
                    String value = (keyValue.length > 1) ? URLDecoder.decode(keyValue[1], encoding) : null;
                    add(key, value);
                } catch (UnsupportedEncodingException ex) {
                    throw AssertUtil.fail();
                }
            }
        }

        private void add(String name, String value) {
            List<String> values = paramMap.get(name);
            if (values == null) {
                values = new ArrayList<String>();
                paramMap.put(name, values);
            }
            values.add(value);
        }

        /**
         * Returns the current parameter map.
         * 
         * @return as descrived above
         */
        public Map<String, List<String>> getParamMap() {
            return paramMap;
        }

        /**
         * Returns the first value of the specified parameter.
         * 
         * @param name parameter name
         * @return first value of the parameter, or null if the parameter does not
         *         exist.
         */
        public String getParam(String name) {
            List<String> values = getParams(name);
            return values == null ? null : values.get(0);
        }

        /**
         * Returns the values of the specified parameter.
         * 
         * @param name parameter name
         * @return values of the parameter, or null if the parameter does not exist.
         */
        public List<String> getParams(String name) {
            return paramMap.get(name);
        }

    }
    
    /**
     * Builder of HTTP query string.
     * 
     * @author tomoto
     */
    public static class Builder {
        private StringBuilder result = new StringBuilder();
        private String encoding;
        
        public Builder(String encoding) {
            ParamUtil.check(Charset.isSupported(encoding));
            this.encoding = encoding;
        }
        
        public Builder() {
            this("UTF-8");
        }
        
        public <T> void addValue(String name, String value) {
            if (result.length() > 0) {
                result.append("&");
            }
            
            try {
                result.append(URLEncoder.encode(name, encoding));
                if (value != null) {
                    result.append("=");
                    result.append(URLEncoder.encode(value, encoding));
                }
            } catch (UnsupportedEncodingException e) {
                throw AssertUtil.fail();
            }
        }
        
        public void addValues(String name, Collection<?> values) {
            for (Object value : values) {
                addValue(name, ObjectUtils.toString(value, null));
            }
        }
        
        public void addSingleValueMap(Map<String, ?> map) {
            for (Entry<String, ?> entry : map.entrySet()) {
                addValue(entry.getKey(), ObjectUtils.toString(entry.getValue(), null));
            }
        }
        
        public void addMultiValueMap(Map<String, ? extends Collection<?>> map) {
            for (Entry<String, ? extends Collection<?>> entry : map.entrySet()) {
                addValues(entry.getKey(), entry.getValue());
            }
        }
        
        public void clear() {
            result = new StringBuilder();
        }
        
        @Override
        public String toString() {
            return result.toString();
        }
        
    }
}
